/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.spring.factory;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import com.genia.toolbox.basics.process.Comparators;

/**
 * a cglib {@link net.sf.cglib.proxy.Callback} that allows to deferr the final
 * instanciation of the {@link List} of all the beans implementing an interface
 * until the bean factory is completly initialized.
 */
public class DeferredBeanListMethodInterceptor
    implements MethodInterceptor, ApplicationListener, BeanFactoryAware
{

  /**
   * the {@link ListableBeanFactory} to use.
   */
  private transient ListableBeanFactory beanFactory;

  /**
   * the {@link List} that will hold the beans when the bean factory is
   * initialized.
   */
  private transient List<Object> backingList = null;

  /**
   * the {@link Class} of the beans to retrieve.
   */
  private Class<?> beanClass;

  /**
   * is the bean factory initialized.
   */
  private boolean initialized = false;



  /**
   * All generated proxied methods call this method instead of the original
   * method. The original method may either be invoked by normal reflection
   * using the Method object, or by using the MethodProxy (faster).
   * 
   * @param object
   *          "this", the enhanced object
   * @param method
   *          intercepted Method
   * @param arguments
   *          argument array; primitive types are wrapped
   * @param methodProxy
   *          used to invoke super (non-intercepted method); may be called as
   *          many times as needed
   * @throws Throwable
   *           any exception may be thrown; if so, super method will not be
   *           invoked
   * @return any value compatible with the signature of the proxied method.
   *         Method returning void will ignore this value.
   * @see net.sf.cglib.proxy.MethodProxy
   * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
   *      java.lang.reflect.Method, java.lang.Object[],
   *      net.sf.cglib.proxy.MethodProxy)
   */
  public Object intercept(Object object, Method method, Object[] arguments, MethodProxy methodProxy)
      throws Throwable
  {
    return methodProxy.invoke(getBeansList(), arguments);
  }



  /**
   * return the {@link List} of beans to use in the current context.
   * 
   * @return the {@link List} of beans to use in the current context
   */
  private List<Object> getBeansList()
  {
    if (!isInitialized()) {
      return computeBeansList();
    }
    if (backingList == null) {
      backingList = computeBeansList();
    }
    return backingList;
  }



  /**
   * computes the {@link List} of beans to use in the current context.
   * 
   * @return the {@link List} of beans to use in the current context
   */
  @SuppressWarnings("unchecked")
  private List<Object> computeBeansList()
  {
    List<Object> elements = new ArrayList<Object>();
    elements.addAll(beanFactory.getBeansOfType(beanClass).values());
    Collections.sort(elements, Comparators.ORDERED_COMPARATOR);
    return elements;
  }



  /**
   * getter for the initialized property.
   * 
   * @return the initialized
   */
  public boolean isInitialized()
  {
    return initialized;
  }



  /**
   * setter for the initialized property.
   * 
   * @param initialized
   *          the initialized to set
   */
  public void setInitialized(boolean initialized)
  {
    this.initialized = initialized;
  }



  /**
   * This method is called on each event on the
   * {@link  org.springframework.context.ApplicationContext}. It is used to
   * call all the initialization beans.
   * 
   * @param applicationEvent
   *          the event being fired *
   * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
   */
  public void onApplicationEvent(ApplicationEvent applicationEvent)
  {
    if (applicationEvent instanceof ContextRefreshedEvent) {
      setInitialized(true);
    }
  }



  /**
   * Callback that supplies the owning factory to a bean instance.
   * <p>
   * Invoked after the population of normal bean properties but before an
   * initialization callback such as
   * {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()}
   * or a custom init-method.
   * 
   * @param beanFactory
   *          owning BeanFactory (never <code>null</code>). The bean can
   *          immediately call methods on the factory.
   * @see org.springframework.beans.factory.BeanInitializationException
   * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
   */
  public void setBeanFactory(BeanFactory beanFactory)
  {
    this.beanFactory = (ListableBeanFactory) beanFactory;
  }



  /**
   * getter for the objectClass property.
   * 
   * @return the objectClass
   */
  public Class<?> getBeanClass()
  {
    return beanClass;
  }



  /**
   * setter for the objectClass property.
   * 
   * @param beanClass
   *          the objectClass to set
   */
  public void setBeanClass(Class<?> beanClass)
  {
    this.beanClass = beanClass;
  }

}
